package com.bigknow.appstore.web.framework.dao;

import com.bigknow.appstore.common.AppStoreRuntimeException;
import com.bigknow.appstore.web.framework.entity.IEntity;
import com.bigknow.appstore.web.framework.vo.Page;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.query.Query;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 支持类型转换的dao
 * Created by chaos on 2016/3/15.
 */
public abstract class AbstractConvertDao<E extends Object, T extends IEntity> implements IDao<T> {

    protected abstract T toEntity(E object);

    protected abstract E toObject(T entity);

    private Class<E> objectClass = (Class<E>) ((ParameterizedType) getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0];

    private Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass()
            .getGenericSuperclass()).getActualTypeArguments()[1];

    private List<Field> queryFields = findQueryFields();

    @Autowired
    protected Datastore ds;

    @Override
    public T get(String id) {
        E object = ds.get(objectClass, id);
        return toEntity(object);
    }

    /**
     * No include id field.Because query id use method {@method get}.
     *
     * @return
     */
    private List<Field> findQueryFields() {
        List<Field> fields = new ArrayList<>();
        Field[] entityFields = entityClass.getDeclaredFields();
        for (int i = 0; i < entityFields.length; i++) {
            Field field = entityFields[i];
            if (field.getType().isPrimitive()
                    || field.getType().getSimpleName().equalsIgnoreCase("String")) {
                // Only need primitive type and String type
                fields.add(field);
            }
        }
        return fields;
    }

    private List<Field> getQueryFields() {
        return queryFields;
    }

    @Override
    public List<T> find(T t) {
        Query<E> q = ds.find(objectClass);
        getQueryFields().forEach(field -> {
            try {
                if (field.get(t) != null)
                    q.field(field.getName()).equal(field.get(t));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
        return q.asList().stream().map(e -> toEntity(e)).collect(Collectors.toList());
    }

    @Override
    public T findFirst(T t) {
        List<T> list = find(t);
        if (list != null && !list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public Page<T> find(T t, int pageIndex, int pageSize) {

        Query<E> query = ds.createQuery(objectClass);

        return toPage(query.offset((pageIndex - 1) * pageSize).limit(pageSize).asList()
                .stream().map(e -> toEntity(e)).collect(Collectors.toList()),pageIndex,pageSize);
    }

    private Page<T> toPage(List<T> list, int pageIndex, int pageSize) {
        Page<T> page = new Page<>();
        page.setTotalNum(list.size());
        page.setEntities(list);
        page.setPageIndex(pageIndex);
        page.setPageSize(pageSize);

        long pagesNum = page.getTotalNum() / pageSize;
        if (page.getTotalNum() % pageSize != 0) pagesNum++;
        page.setPagesNum(pagesNum);

        page.setStartRow(pageIndex * pageSize - (pageSize - 1));
        if ((pageIndex) * pageSize >= page.getTotalNum()) page.setEndRow(page.getTotalNum());
        else page.setEndRow((pageIndex) * pageSize);
        return page;
    }

    @Override
    public List<T> findWithFuzzy(T t) {
        return this.find(t);
    }

    @Override
    public Page<T> findWithFuzzy(T t, int pageIndex, int pageSize) {
        return this.find(t,pageIndex,pageSize);
    }

    @Override
    public List<T> findAll() {
        return ds.createQuery(objectClass).asList().stream().map(e -> toEntity(e)).collect(Collectors.toList());
    }

    @Override
    public int save(T t) {
        E object = toObject(t);
        ds.save(object);
        return 1;
    }

    @Override
    public int delete(String id) {
        return ds.delete(objectClass, id).getN();
    }

    @Override
    public long getCount() {
        return ds.getCount(objectClass);
    }

    @Override
    public long getCount(T t) {
        return ds.getCount(objectClass);
    }

    @Override
    public List<T> findById(String... ids) {
        throw new AppStoreRuntimeException("Not implement");
    }

    @Override
    public List<T> find(T t, int limit) {
        return null;
    }

    @Override
    public List<T> findLimit(int limit) {
        return null;
    }
}
